﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;

using FXnaEngine.Graphics;

using Microsoft.Xna.Framework.Graphics;




namespace FXnaEngine
{
    public class FTrackLine : FMeshProvider
    {


        public FTrackLine(List<Vector3> keyVectors)
        {
            this.inputPoints = new List<Vector3>();
            this.inputPoints.AddRange(keyVectors);

            this.Load();

        }





        public FTrackLine()
        {
            
            //给出一个默认的点，用于测试


            this.inputPoints = new List<Vector3> { 

                
                 new Vector3(-10, -10, 0),
                  new Vector3(-5, -7, 0),
                   new Vector3(-2, 0, 0),
                     new Vector3(3,5, 0),
                     new Vector3(9,2, 0),
                       new Vector3(10,10, 0),
                        
                           new Vector3(9,2, 0),

                            new Vector3(-10,-10, 0),




            };

            this.Load();




        }




        /// <summary>
        /// Number of iterations we will produce from the input points to
        /// get our line positions. All data is generated with help of
        /// CattmullRom splines.
        /// </summary>
        protected const int NumberOfIterationsPer100Meters = 40;

        /// <summary>
        /// Curve factor, 1.0 will make all curves the same as the rotations are.
        /// Basically we just have to drive ahead. Reduce this value to make
        /// the track harder!
        /// </summary>
        const float CurveFactor = 0.25f;
        /// <summary>
        /// Correct the road to be up each step. This is important to
        /// make our road face always up except where we have no choice (loopings).
        /// </summary>
        const float UpFactorCorrector = 0.6f;

        /// <summary>
        /// Factor for streching the road texture, smaller values will the texture
        /// strech more.
        /// </summary>
        const float RoadTextureStrechFactor = 0.125f;

        /// <summary>
        /// Number of values we put into our second pass up vector smoothing
        /// alsorithm below.
        /// </summary>
        const int NumberOfUpSmoothValues = 10;

        /// <summary>
        /// Minimium distance of the road track to the landscape.
        /// </summary>
        const float MinimumLandscapeDistance = 2.0f;



        List<Vector3> inputPoints;


        /// <summary>
        /// 用于输入的关键点
        /// </summary>
        public List<Vector3> InputPoints
        {
            get { return inputPoints; }

        }





        /// <summary>
        /// 经过差值，光滑等算法后的实际点，当然也可以不应用哪些算法
        /// </summary>
        List<TrackVertex> points = new List<TrackVertex>();

        public List<TrackVertex> Points
        {
            get { return points; }

        }



        protected void Load()
        {

            this.points.Clear();


            #region Generate all points with help of catmull rom splines
            // Generate all points with help of catmull rom splines
            for (int num = 0; num < inputPoints.Count; num++)
            {
                // Get the 4 required points for the catmull rom spline
                Vector3 p1 = inputPoints[num - 1 < 0 ? inputPoints.Count - 1 : num - 1];
                Vector3 p2 = inputPoints[num];
                Vector3 p3 = inputPoints[(num + 1) % inputPoints.Count];
                Vector3 p4 = inputPoints[(num + 2) % inputPoints.Count];

                // Calculate number of iterations we use here based
                // on the distance of the 2 points we generate new points from.
                float distance = Vector3.Distance(p2, p3);
                int numberOfIterations =
                    (int)(NumberOfIterationsPer100Meters * (distance / 100.0f));
                if (numberOfIterations <= 0)
                    numberOfIterations = 1;

                for (int iter = 0; iter < numberOfIterations; iter++)
                {
                    TrackVertex newVertex = new TrackVertex(
                        Vector3.CatmullRom(p1, p2, p3, p4,
                        iter / (float)numberOfIterations));

                    points.Add(newVertex);
                }
            }

            #endregion


            #region Generate up vectors, very important for our road building
            // Pre up vectors are used to first generate all optimal up vectors
            // for the track, but this is not useful for driving because we need
            // the road to point up always except for loopings.
            List<Vector3> preUpVectors = new List<Vector3>();

            // Now generate all up vectors, first pass does optimal up vectors.
            Vector3 defaultUpVec = new Vector3(0, 0, 1);
            Vector3 lastUpVec = defaultUpVec;
            for (int num = 0; num < points.Count; num++)
            {
                // Get direction we are driving in at this point,
                // interpolate with help of last and next points.
                Vector3 dir = points[(num + 1) % points.Count].pos -
                    points[num - 1 < 0 ? points.Count - 1 : num - 1].pos;
                dir.Normalize();

                // Now calculate the optimal up vector for this point
                Vector3 middlePoint = (points[(num + 1) % points.Count].pos +
                    points[num - 1 < 0 ? points.Count - 1 : num - 1].pos) / 2.0f;
                Vector3 optimalUpVector = middlePoint - points[num].pos;
                if (optimalUpVector.Length() < 0.0001f)
                    optimalUpVector = lastUpVec;
                optimalUpVector.Normalize();

                // Store the optimalUpVectors in the preUpVectors list
                preUpVectors.Add(optimalUpVector);

                // Also save dir vector
                points[num].dir = dir;

                // And remember the last upVec in case the road is going straight ahead
                lastUpVec = optimalUpVector;
            }

            // Interpolate the first up vector for a smoother road at the start pos
            preUpVectors[0] = preUpVectors[preUpVectors.Count - 1] + preUpVectors[1];
            preUpVectors[0].Normalize();
            #endregion

            #region Interpolate the up vectors and also add the dir and right vectors
            // Second pass, interpolated precalced values and apply our logic :)
            //preUpVectors[0] =
            lastUpVec = Vector3.Lerp(defaultUpVec, preUpVectors[0],
                1.5f * CurveFactor * UpFactorCorrector);
            //lastUpVec = preUpVectors[0];
            Vector3 lastUpVecUnmodified = lastUpVec;// defaultUpVec;
            for (int num = 0; num < points.Count; num++)
            {
                // Grab dir vector (could be calculated here too)
                Vector3 dir = points[num].dir;

                // First of all interpolate the preUpVectors
                Vector3 upVec =
                    //single input: preUpVectors[num];
                    Vector3.Zero;
                for (int smoothNum = -NumberOfUpSmoothValues / 2;
                    smoothNum <= NumberOfUpSmoothValues / 2; smoothNum++)
                    upVec +=
                        preUpVectors[(num + points.Count + smoothNum) % points.Count];
                upVec.Normalize();

                // Find out if this road piece is upside down and if we are
                // moving up or down. This is VERY important for catching loopings.
                bool upsideDown = upVec.Z < -0.25f &&
                    lastUpVecUnmodified.Z < -0.05f;
                bool movingUp = dir.Z > 0.75f;
                bool movingDown = dir.Z < -0.75f;

                // Mix in the last vector to make curves weaker
                upVec = Vector3.Lerp(lastUpVec, upVec, CurveFactor);
                upVec.Normalize();
                // Store the last value to check for loopings.
                lastUpVecUnmodified = upVec;

                // Don't mix in default up if we head up or are upside down!
                // Its very useful to know if we move up or down to fix the
                // problematic areas at loopings by pointing stuff correct right away.
                if (movingUp)
                    lastUpVec = Vector3.Lerp(upVec, -defaultUpVec, UpFactorCorrector);
                else if (movingDown)
                    lastUpVec = Vector3.Lerp(upVec, defaultUpVec, UpFactorCorrector);
                else if (upsideDown)
                    lastUpVec = Vector3.Lerp(upVec, -defaultUpVec, UpFactorCorrector);
                else
                    lastUpVec = Vector3.Lerp(upVec, defaultUpVec, UpFactorCorrector);



                //特殊的渲染技巧，但是这里我们还没有完全设计好地形逻辑，所以这里暂时等待



                // If we are very close to the ground, make the road point up more!


                //if (//upsideDown == false &&
                //    landscape != null)
                //{
                //    // Get landscape height here
                //    float landscapeHeight = landscape.GetMapHeight(
                //        points[num].pos.X, points[num].pos.Y);

                //    // If point is close to the landscape, let everything point up more
                //    if (points[num].pos.Z - landscapeHeight <
                //        MinimumLandscapeDistance * 4)
                //        lastUpVec = Vector3.Lerp(upVec, defaultUpVec,
                //            1.75f * UpFactorCorrector);
                //}





                // And finally calculate rightVectors with just a cross product.
                // Used to render the track later.
                Vector3 rightVec = Vector3.Cross(dir, upVec);
                rightVec.Normalize();
                points[num].right = rightVec;

                // Recalculate up vector with help of right and dir.
                // This makes the up vector to always point up 90 degrees.
                upVec = Vector3.Cross(rightVec, dir);
                upVec.Normalize();
                points[num].up = upVec;
            }
            #endregion

            #region Smooth up vectors!
            lastUpVec = points[0].up;
            for (int num = 0; num < points.Count; num++)
                preUpVectors[num] = points[num].up;
            for (int num = 0; num < points.Count; num++)
            {
                // Interpolate up vectors again
                Vector3 upVec = Vector3.Zero;
                for (int smoothNum = -NumberOfUpSmoothValues;
                    smoothNum <= NumberOfUpSmoothValues; smoothNum++)
                {
                    upVec +=
                        preUpVectors[(num + points.Count + smoothNum) % points.Count];
                }
                upVec.Normalize();
                points[num].up = upVec;

                // Also rebuild right vector
                Vector3 dir = points[num].dir;
                points[num].right = Vector3.Cross(dir, upVec);
            }
            #endregion



            GenerateUTextureCoordinates();



        }



        private void GenerateUTextureCoordinates()
        {
            #region Generate u texture coordinates
            float currentRoadUTexValue = 0.0f;
            for (int num = 0; num < points.Count; num++)
            {
                // Assign u texture coordinate
                points[num].uv.X = currentRoadUTexValue;

                // Uniform calculation of the texture coordinates for the roadway,
                // so it doesn't matter if there is a gap of 2 or 200 m
                currentRoadUTexValue += RoadTextureStrechFactor *
                    (points[(num + 1) % points.Count].pos -
                    points[num % points.Count].pos).Length();
            }

            // Now we got a problem, for the polygons between the last and the first
            // points (last road block) we might have very different texture
            // coordinates, which may look very wrong. To fix this we generate a new
            // point by just duplicating the first point and applying another set of
            // texture coordinates!
            points.Add(new TrackVertex(
                points[0].pos,
                points[0].right,
                points[0].up,
                points[0].dir,
                new Vector2(currentRoadUTexValue, 0),
                points[0].roadWidth));
            #endregion
        }






        public override void BuildMesh()
        {
           

            #region Generate the road vertices
            // Each road segment gets 5 points:
            // left, left middle, middle, right middle, right.
            // The reason for this is that we would bad triangle errors if the
            // road gets wider and wider. This happens because we need to render
            // quad, but we can only render triangles, which often have different
            // orientations, which makes the road very bumpy. This still happens
            // with 8 polygons instead of 2, but it is much better this way.
            // Another trick is not to do so much iterations in TrackLine, which
            // causes this problem. Better to have a not so round track, but at
            // least the road up/down itself is smooth.
            // The last point is duplicated (see TrackLine) because we have 2 sets
            // of texture coordinates for it (begin block, end block).
            // So for the index buffer we only use points.Count-1 blocks.
            TangentVertex[] roadVertices  = new TangentVertex[this.Points.Count * 5];

            // Current texture coordinate for the roadway (in direction of movement)
            for (int num = 0; num < Points.Count; num++)
            {
                // Get vertices with help of the properties in the TrackVertex class.
                // For the road itself we only need vertices for the left and right
                // side, which are vertex number 0 and 1.
                roadVertices[num * 5 + 0] = Points[num].RightTangentVertex;
                roadVertices[num * 5 + 1] = Points[num].MiddleRightTangentVertex;
                roadVertices[num * 5 + 2] = Points[num].MiddleTangentVertex;
                roadVertices[num * 5 + 3] = Points[num].MiddleLeftTangentVertex;
                roadVertices[num * 5 + 4] = Points[num].LeftTangentVertex;
            }

            // fix
            //roadVb = new VertexBuffer(
            //    BaseGame.Device,
            //    typeof(TangentVertex),
            //    roadVertices.Length,
            //    ResourceUsage.WriteOnly,
            //    ResourceManagementMode.Automatic);
            VertexBuffer roadVb = new VertexBuffer(
                FEngineState.Device,
                typeof(TangentVertex),
                roadVertices.Length,
                BufferUsage.WriteOnly);
            roadVb.SetData(roadVertices);


            //自行代码


            this.InnerMesh.VertexBuffer = roadVb;


            // Also calculate all indices, we have 8 polygons for each segment with
            // 3 vertices each. We got 1 segment less than points because the
            // last point is duplicated (different tex coords).
            int[] indices = new int[(Points.Count - 1) * 8 * 3];
            int vertexIndex = 0;
            for (int num = 0; num < Points.Count - 1; num++)
            {
                // We only use 3 vertices (and the next 3 vertices),
                // but we have to construct all 24 indices for our 4 polygons.
                for (int sideNum = 0; sideNum < 4; sideNum++)
                {
                    // Each side needs 2 polygons.

                    // 1. Polygon
                    indices[num * 24 + 6 * sideNum + 0] =
                        vertexIndex + sideNum;
                    indices[num * 24 + 6 * sideNum + 1] =
                        vertexIndex + 5 + 1 + sideNum;
                    indices[num * 24 + 6 * sideNum + 2] =
                        vertexIndex + 5 + sideNum;

                    // 2. Polygon
                    indices[num * 24 + 6 * sideNum + 3] =
                        vertexIndex + 5 + 1 + sideNum;
                    indices[num * 24 + 6 * sideNum + 4] =
                        vertexIndex + sideNum;
                    indices[num * 24 + 6 * sideNum + 5] =
                        vertexIndex + 1 + sideNum;
                }

                // Go to the next 5 vertices
                vertexIndex += 5;
            }

            // Set road back index buffer
            // fix
            //roadIb = new IndexBuffer(
            //    BaseGame.Device,
            //    typeof(int),
            //    indices.Length,
            //    ResourceUsage.WriteOnly,
            //    ResourceManagementMode.Automatic);
            IndexBuffer roadIb = new IndexBuffer(
                FEngineState.Device,
                typeof(int),
                indices.Length,
                BufferUsage.WriteOnly);
            roadIb.SetData(indices);



            InnerMesh.IndexBuffer = roadIb;

            #endregion


            //更新其他变量

            this.InnerMesh.PrimitiveType = PrimitiveType.TriangleList;

            this.InnerMesh.NumberOfVertices = this.Points.Count;

            this.InnerMesh.NumberOfPrimitives = (Points.Count - 1) * 8;



        }


    }
}
